import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;

/**
 * The thread that simulates real time and notifies observers of simulated time
 * events
 * 
 * @author SNelson
 * 
 */
public class Scheduler extends Thread
{
	/**
	 * The number of days in the simulation
	 */
	private final int numDays;
	/**
	 * The observers that will be notified when a new time event happens
	 */
	private List<Schedulable> schedulables = new ArrayList<Schedulable>();

	/**
	 * The number of hours in a day
	 */
	public static final int HOURS_PER_DAY = 24;
	/**
	 * The length of a simulated day in milliseconds
	 */
	public static final int LENGTH_OF_A_DAY = 1000;

	/**
	 * The current simulated day
	 */
	private int day = 0;
	/**
	 * The current simulated hour
	 */
	private int hour = 0;
	/**
	 * The current simulated minute
	 */
	private int minute = 0;

	/**
	 * Constructor
	 * 
	 * @param numDays
	 *            The number of days to simulate
	 */
	public Scheduler(int numDays)
	{
		this.numDays = numDays;
	}

	/**
	 * Adds the given schedulable to be notified of simulated time events
	 * 
	 * @param schedulable
	 *            The schedulable to add
	 */
	public void addSchedulable(Schedulable schedulable)
	{
		schedulables.add(schedulable);
	}

	/**
	 * Removes the given schedulable to be notified of simulated time events
	 * 
	 * @param schedulable
	 *            The schedulable to remove
	 */
	public void removeSchedulable(Schedulable schedulable)
	{
		schedulables.remove(schedulable);
	}

	/**
	 * Gets a formatted string representing the current simulated time
	 * 
	 * @return A formatted string representation of the current simulated time
	 */
	public synchronized String getFormattedTime()
	{
		GregorianCalendar calendar = new GregorianCalendar();
		calendar.set(GregorianCalendar.DAY_OF_MONTH, day + 1);
		calendar.set(GregorianCalendar.HOUR_OF_DAY, hour);
		calendar.set(GregorianCalendar.MINUTE, minute);
		return "Day "
			+ new SimpleDateFormat("d HH:mm").format(calendar.getTime());
	}

	/**
	 * The body of this thread. Runs the simulation
	 */
	@Override
	public void run()
	{
		for (day = 0; day < numDays; day++)
		{
			for (Schedulable schedulable : schedulables)
			{
				schedulable.onNewDay(day);
			}
			for (hour = 0; hour < HOURS_PER_DAY; hour++)
			{
				for (Schedulable schedulable : schedulables)
				{
					schedulable.onHour(day, hour);
				}
				try
				{
					sleep(LENGTH_OF_A_DAY / HOURS_PER_DAY);
				}
				catch (InterruptedException e)
				{
					e.printStackTrace();
				}
			}
		}
		List<Schedulable> copy = new ArrayList<Schedulable>(schedulables);
		for (Schedulable schedulable : copy)
		{
			schedulable.onSimulationEnd();
		}
	}
}
